package org.fujene.memcon;

import java.io.File;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

/** This class holds informations of memory-block on disk. */
public class DiskController{
	// Directory for storing memory-block
	private String dir = null;
	private int fileSize;

	private SortedMap<ClusterTuple, FileBufferTuple> fileMap = null;

	public boolean isExistInList(int cluster, int block){
		Set<ClusterTuple> keys = fileMap.keySet();

		for(ClusterTuple tuple: keys){
			if(tuple.cluster() == cluster && tuple.block() == block) return true;
		}

		return false;
	}

	public SortedMap<ClusterTuple, FileBufferTuple> getFileMap(){
		reloadFileList();
		return fileMap;
	}

	public DiskController(String dir, int fileSize){
		assert dir != null;

		this.fileMap =
			Collections.synchronizedSortedMap(new TreeMap<ClusterTuple, FileBufferTuple>(
				new Comparator<ClusterTuple>(){
					@Override
					public int compare(ClusterTuple o1, ClusterTuple o2){
						if(o1.cluster() > o2.cluster()) return 1;
						else if(o1.cluster() < o2.cluster()) return -1;
						else{
							if(o1.block() > o2.block()) return 1;
							else if(o1.block() < o2.block()) return -1;
						}
						return 0;
					}
				}));
		this.fileSize = fileSize;

		setDir(dir);
	}

	public void setDir(String dir){
		if(!dir.endsWith("/")) dir.concat("/");
		// Check whether directory exists
		File f = new File(dir);

		if(f.exists() && f.isDirectory()){
			this.dir = dir;
			reloadFileList();
		}else if(f.exists() && !f.isDirectory()) System.out
			.println("Specified directory name exists, but not a directory.");
		else
		// No directory, create it
		if(f.mkdir()) this.dir = dir;
	}

	public File getDescriptor(int clusterID, int blockID){
		return fileMap.get(new ClusterTuple(clusterID, blockID)).descriptor();
	}
	
	public FileChannel getChannel(int clusterID, int blockID) {
		ClusterTuple findTuple = new ClusterTuple(clusterID, blockID);
		FileBufferTuple fileTuple = fileMap.get(findTuple);
		if(fileTuple == null){
			String newFileName = String.format("%s%d_%d.blk", dir, clusterID, blockID);
			fileTuple = FileBufferTuple.create(newFileName, fileSize);
			fileMap.put(findTuple, fileTuple);
		}
		return fileTuple.channel();
	}

	public ByteBuffer getBuffer(int clusterID, int blockID){
		ClusterTuple findTuple = new ClusterTuple(clusterID, blockID);
		FileBufferTuple fileTuple = fileMap.get(findTuple);
		if(fileTuple == null){
			String newFileName = String.format("%s%d_%d.blk", dir, clusterID, blockID);
			fileTuple = FileBufferTuple.create(newFileName, fileSize);
			fileMap.put(findTuple, fileTuple);
		}
		return fileTuple.buffer();
	}

	// Refresh file list
	private void reloadFileList(){
		File dirf = new File(dir);

		if(dirf.exists() && dirf.isDirectory()){
			// Find file named "xxx.blk"
			String[] fnamelist = dirf.list();

			// There's no file in the specified directory
			if(fnamelist == null) return;

			for(String str: fnamelist){
				if(!str.endsWith("blk")) continue;
				fileMap.put(ClusterTuple.fromFile(str), FileBufferTuple.create(this.dir + str, fileSize));
			}
		}
	}

	public String changeNameToBak(int ID){
		File changeFile = fileMap.get(Integer.valueOf(ID)).descriptor();
		changeFile.renameTo(new File(changeFile.getAbsolutePath() + ".bak"));
		return changeFile.getAbsolutePath();
	}

	// Drop file with specified ID
	public void dropFile(int ID){
		// Check whether specified ID exists
		Integer IDobj = Integer.valueOf(ID);
		if(fileMap.containsKey(IDobj)){
			File delFile = fileMap.get(IDobj).descriptor();
			delFile.delete();
			fileMap.remove(IDobj);
		}
	}

	// Drop all file
	public void dropAll(){
		for(Entry<ClusterTuple, FileBufferTuple> eachFile: fileMap.entrySet())
			eachFile.getValue().descriptor().delete();
	}

	public int getItemNum(){
		return fileMap.size();
	}

	public String getDir(){
		return dir;
	}
}